package com.beemans.topon.ads

import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.annotation.Keep
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.anythink.core.api.ATAdInfo
import com.anythink.core.api.ATNetworkConfirmInfo
import com.anythink.core.api.AdError
import com.anythink.rewardvideo.api.ATRewardVideoAd
import com.anythink.rewardvideo.api.ATRewardVideoExListener
import com.beemans.topon.kts.context
import com.beemans.topon.kts.deepClone
import com.beemans.topon.kts.lifecycleOwnerEx
import com.beemans.topon.log.Logger
import kotlinx.coroutines.Job
import java.util.concurrent.ConcurrentHashMap

/**
 * @author ti
 * @date 2022/10/26.
 */
object TpRewardAd {
    private val logTag by lazy { this::class.java.simpleName }
    private val nextAdLoaderMap by lazy { ConcurrentHashMap<String, MutableList<TpRewardAdLoader>>() }
    private val allAdLoaderMap by lazy { ConcurrentHashMap<String, MutableList<TpRewardAdLoader>>() }

    /**
     * 开始加载广告
     *
     * @param owner 传入 Activity 或 Fragment
     * @param config 广告配置
     * @param rewardAdCallback 广告回调
     */
    fun start(
        owner: LifecycleOwner,
        config: TpRewardAdConfig,
        rewardAdCallback: (TpRewardAdCallback.() -> Unit)? = null
    ) {
        start(TpRewardAdLoader(owner, config, rewardAdCallback))
    }

    /**
     * 开始加载广告
     *
     * @param loader 广告加载器
     */
    fun start(loader: TpRewardAdLoader) {
        if (!loader.config.isAllowFullAdCover) {
            //防止全屏广告多次加载导致叠加展示
            var isFullAdLoading = loader.isAdRequesting() || loader.isAdShowing()
            if (!isFullAdLoading) {
                isFullAdLoading = !loader.config.isOnlyPreload
                        && (isAdLoading(loader) || TpSplashAd.isAdLoading() || TpInsertAd.isAdLoading())
            }
            if (isFullAdLoading) {
                Logger.logI(logTag, "onAdStartFailed")
                loader.callback?.onAdStartFailed?.invoke()
                return
            }
        }
        addLoader(loader, nextAdLoaderMap)
        val allLoaders = addLoader(loader, allAdLoaderMap)
        //当前有其他相同广告位正在加载广告时，等待加载
        if (allLoaders.any { it.isAdRequesting() }) {
            loader.startAdTimeOutHandle()
            return
        }
        loader.start()
    }

    /**
     * 是否正在加载广告（广告请求 + 广告展示）
     */
    fun isAdLoading(): Boolean {
        return isAdLoading(null)
    }

    /**
     * 是否正在展示广告
     */
    fun isAdShowing(): Boolean {
        return isAdShowing(null)
    }

    private fun isAdLoading(loader: TpRewardAdLoader?): Boolean {
        return allAdLoaderMap.values.any { list ->
            list.any { it != loader && !it.config.isOnlyPreload }
        }
    }

    private fun isAdShowing(loader: TpRewardAdLoader?): Boolean {
        return allAdLoaderMap.values.any { list ->
            list.any { it != loader && it.isAdShowing() }
        }
    }

    /**
     * 开始加载列表中最近的一个
     */
    private fun startNext(oldLoader: TpRewardAdLoader) {
        val placementId = oldLoader.config.placementId
        val loaders = nextAdLoaderMap[placementId]
        loaders?.lastOrNull()?.let { start(it) }
    }

    /**
     * 添加广告加载器
     */
    private fun addLoader(
        loader: TpRewardAdLoader,
        loaderMap: ConcurrentHashMap<String, MutableList<TpRewardAdLoader>>
    ): List<TpRewardAdLoader> {
        val placementId = loader.config.placementId
        val loaders = loaderMap.getOrPut(placementId) { mutableListOf() }
        if (!loaders.contains(loader)) {
            loaders.add(loader)
            loaderMap[placementId] = loaders
        }
        return loaders
    }

    /**
     * 从列表中移除对应的加载
     */
    private fun removeLoader(
        loader: TpRewardAdLoader,
        loaderMap: ConcurrentHashMap<String, MutableList<TpRewardAdLoader>>
    ) {
        val placementId = loader.config.placementId
        val loaders = loaderMap[placementId]
        if (loaders.isNullOrEmpty()) return
        loaders.remove(loader)
        loaderMap[placementId] = loaders
    }

    /**
     * 广告加载器
     *
     * @param owner 传入 Activity 或 Fragment
     * @param config 广告配置
     * @param rewardAdCallback 广告回调
     */
    class TpRewardAdLoader(
        private val owner: LifecycleOwner,
        val config: TpRewardAdConfig,
        rewardAdCallback: (TpRewardAdCallback.() -> Unit)? = null
    ) : DefaultLifecycleObserver, ATRewardVideoExListener {
        private val adsHandler by lazy { Handler(Looper.getMainLooper()) }

        private val context by lazy { owner.context }
        private val placementId by lazy { config.placementId }

        private var atRewardVideoAd: ATRewardVideoAd? = null
        private var tpAdStatus: TpRewardAdStatus? = null
        private var renderJob: Job? = null

        var callback = rewardAdCallback?.let { TpRewardAdCallback().apply(it) }
            private set

        init {
            init()
        }

        private fun init() {
            owner.lifecycleOwnerEx.lifecycle.addObserver(this)
        }

        override fun onRewardedVideoAdLoaded() {
            onAdLoaded()
        }

        override fun onRewardedVideoAdFailed(adError: AdError?) {
            onAdLoadFailed(adError, false)
        }

        override fun onRewardedVideoAdPlayStart(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdShow --- atAdInfo:$atAdInfo")
            callback?.onAdShow?.invoke(atAdInfo)

            if (config.isAutoLoadAd && atRewardVideoAd?.isAdReady != true) {
                Logger.logI(logTag, "onAdShow --- autoLoadAd")
                val newConfig = config.deepClone()?.apply { isOnlyPreload = true }
                newConfig?.let { start(owner, it) }
            }
        }

        override fun onRewardedVideoAdPlayEnd(atAdInfo: ATAdInfo?) {
        }

        override fun onRewardedVideoAdPlayFailed(adError: AdError?, atAdInfo: ATAdInfo?) {
        }

        override fun onRewardedVideoAdClosed(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdClose --- atAdInfo:$atAdInfo")
            val isReward = tpAdStatus?.isReward ?: false
            val isAgainReward = tpAdStatus?.isAgainReward ?: false
            callback?.onAdClose?.invoke(atAdInfo, isReward, isAgainReward)
            onAdRelease()
        }

        override fun onRewardedVideoAdPlayClicked(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdClick --- atAdInfo:$atAdInfo")
            callback?.onAdClick?.invoke(atAdInfo)
        }

        override fun onReward(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdReward --- atAdInfo:$atAdInfo")
            tpAdStatus?.isReward = true
            callback?.onAdReward?.invoke(atAdInfo)
        }

        override fun onDeeplinkCallback(atAdInfo: ATAdInfo?, isSuccess: Boolean) {
        }

        override fun onDownloadConfirm(
            context: Context?,
            atAdInfo: ATAdInfo?,
            atNetworkConfirmInfo: ATNetworkConfirmInfo?
        ) {
        }

        override fun onRewardedVideoAdAgainPlayStart(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdAgainShow --- atAdInfo:$atAdInfo")
            callback?.onAdAgainShow?.invoke(atAdInfo)
        }

        override fun onRewardedVideoAdAgainPlayEnd(atAdInfo: ATAdInfo?) {
        }

        override fun onRewardedVideoAdAgainPlayFailed(
            adError: AdError?,
            atAdInfo: ATAdInfo?
        ) {
        }

        override fun onRewardedVideoAdAgainPlayClicked(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdAgainClick --- atAdInfo:$atAdInfo")
            callback?.onAdAgainClick?.invoke(atAdInfo)
        }

        override fun onAgainReward(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdAgainReward --- atAdInfo:$atAdInfo")
            tpAdStatus?.isAgainReward = true
            callback?.onAdAgainReward?.invoke(atAdInfo)
        }

        /**
         * 是否正在请求广告
         */
        fun isAdRequesting(): Boolean {
            return tpAdStatus?.isRequesting == true
        }

        /**
         * 是否正在展示广告
         */
        fun isAdShowing(): Boolean {
            return tpAdStatus?.isShowing == true
        }

        /**
         * 获取广告加载器
         */
        private fun getAtRewardVideoAd(): ATRewardVideoAd {
            return ATRewardVideoAd(context, placementId).apply {
                setAdListener(this@TpRewardAdLoader)
            }
        }

        /**
         * 开始加载广告
         */
        fun start() {
            if (atRewardVideoAd == null) {
                atRewardVideoAd = getAtRewardVideoAd()
            }
            onAdStartCallback()
            startAdTimeOutHandle()

            if (atRewardVideoAd?.isAdReady == true) {
                onAdLoaded()
                return
            }
            tpAdStatus?.isRequesting = true
            atRewardVideoAd?.load()
        }

        /**
         * 广告开始加载回调
         */
        private fun onAdStartCallback() {
            if (tpAdStatus == null) {
                tpAdStatus = TpRewardAdStatus(placementId)
            }
            if (tpAdStatus?.isStarted == false) {
                tpAdStatus?.isStarted = true
                Logger.logI(logTag, "onAdStart")
                callback?.onAdStart?.invoke()
            }
        }

        /**
         * 执行广告加载超时处理
         */
        fun startAdTimeOutHandle() {
            val timeout = config.timeout
            if (timeout <= 0) return
            adsHandler.postDelayed({
                onAdStartCallback()
                onAdLoadFailed(null, true)
            }, timeout)
        }

        /**
         * 展示广告
         */
        private fun showAd() {
            if (!config.isAllowFullAdCover) {
                val isFullAdLoading = isAdShowing(this)
                if (isFullAdLoading) {
                    onAdShowFailed()
                    return
                }
            }
            tpAdStatus?.isShowing = true

            var isShowAd = false
            renderJob?.cancel()
            context?.lifecycleScope?.launchWhenResumed {
                isShowAd = true
                atRewardVideoAd?.show(context)
            }.also {
                renderJob = it
            }?.invokeOnCompletion {
                if (isShowAd || renderJob?.isCancelled == true) return@invokeOnCompletion
                onAdShowFailed()
            }
        }

        /**
         * 广告展示失败
         */
        private fun onAdShowFailed() {
            Logger.logI(logTag, "onAdShowFailed")
            tpAdStatus?.isShowing = false
            callback?.onAdShowFailed?.invoke()

            onAdRelease()
        }

        /**
         * 广告加载成功
         */
        private fun onAdLoaded() {
            onAdLoadComplete {
                Logger.logI(logTag, "onAdLoaded")
                callback?.onAdLoaded?.invoke()

                //进行广告展示
                if (tpAdStatus != null && !config.isOnlyPreload) {
                    showAd()
                }
                startNext(this)
            }
        }

        /**
         * 广告加载失败
         */
        private fun onAdLoadFailed(adError: AdError?, isTimeout: Boolean) {
            onAdLoadComplete {
                Logger.logI(logTag, "onAdLoadFailed --- isTimeout:$isTimeout   adError:$adError")
                callback?.onAdLoadFailed?.invoke(adError)
                onAdRelease()

                if (config.retryCount > 0) {
                    config.retryCount--
                    adsHandler.postDelayed({
                        Logger.logI(logTag, "onAdLoadFailed --- retryCount:${config.retryCount}")
                        start()
                    }, 12000)
                    return@onAdLoadComplete
                }
                startNext(this)
            }
        }

        /**
         * 广告加载完成
         */
        private fun onAdLoadComplete(callback: () -> Unit) {
            tpAdStatus?.isRequesting = false
            adsHandler.removeCallbacksAndMessages(null)

            if (tpAdStatus?.isLoadCompleted == false) {
                tpAdStatus?.isLoadCompleted = true
                removeLoader(this, nextAdLoaderMap)
                if (config.isOnlyPreload) {
                    removeLoader(this, allAdLoaderMap)
                }
                callback.invoke()
            }
        }

        /**
         * 获取缓存信息
         */
        fun checkValidAdCaches(): List<ATAdInfo> {
            val atRewardVideoAd = this.atRewardVideoAd ?: getAtRewardVideoAd()
            return atRewardVideoAd.checkValidAdCaches() ?: listOf()
        }

        /**
         * 广告释放
         */
        private fun onAdRelease() {
            adsHandler.removeCallbacksAndMessages(null)
            removeLoader(this, nextAdLoaderMap)
            removeLoader(this, allAdLoaderMap)
            tpAdStatus = null
            callback = null
        }

        override fun onDestroy(owner: LifecycleOwner) {
            owner.lifecycle.removeObserver(this)
            if (!config.isIgnoreDestroy) {
                config.retryCount = 0
                onAdRelease()
            }
        }
    }

    @Keep
    data class TpRewardAdStatus(
        val placementId: String,                //广告位id
        var isStarted: Boolean = false,         //广告是否已开始加载
        var isRequesting: Boolean = false,      //广告正在加载中
        var isShowing: Boolean = false,         //广告正在展示中
        var isLoadCompleted: Boolean = false,   //广告已加载完成（加载成功 or 加载失败）
        var isReward: Boolean = false,          //广告是否已发放奖励
        var isAgainReward: Boolean = false,     //广告是否已再次发放奖励
    )

    @Keep
    data class TpRewardAdConfig(
        var placementId: String,                        //广告位id
        var timeout: Long = 0,                          //加载超时时间，大于0生效
        var isOnlyPreload: Boolean = false,             //是否仅预加载，不立即展示广告
        var isAutoLoadAd: Boolean = true,               //是否自动预加载广告
        var isAllowFullAdCover: Boolean = false,        //是否允许全屏广告（开屏、插屏、激励视频）叠加展示
        var isIgnoreDestroy: Boolean = false,           //是否忽略页面onDestroy，可继续加载广告（一般用于体外特殊情况）
        var retryCount: Int = 0,                        //广告加载重试次数
    )

    class TpRewardAdCallback {
        var onAdStart: (() -> Unit)? = null
        var onAdStartFailed: (() -> Unit)? = null
        var onAdLoaded: (() -> Unit)? = null
        var onAdLoadFailed: ((adError: AdError?) -> Unit)? = null
        var onAdShow: ((atAdInfo: ATAdInfo?) -> Unit)? = null
        var onAdShowFailed: (() -> Unit)? = null
        var onAdReward: ((atAdInfo: ATAdInfo?) -> Unit)? = null
        var onAdClick: ((atAdInfo: ATAdInfo?) -> Unit)? = null
        var onAdClose: ((atAdInfo: ATAdInfo?, isReward: Boolean, isAgainReward: Boolean) -> Unit)? =
            null
        var onAdAgainShow: ((atAdInfo: ATAdInfo?) -> Unit)? = null
        var onAdAgainClick: ((atAdInfo: ATAdInfo?) -> Unit)? = null
        var onAdAgainReward: ((atAdInfo: ATAdInfo?) -> Unit)? = null

        fun onAdStart(onAdStart: () -> Unit) {
            this.onAdStart = onAdStart
        }

        fun onAdStartFailed(onAdStartFailed: () -> Unit) {
            this.onAdStartFailed = onAdStartFailed
        }

        fun onAdLoaded(onAdLoaded: () -> Unit) {
            this.onAdLoaded = onAdLoaded
        }

        fun onAdLoadFailed(onAdLoadFailed: (adError: AdError?) -> Unit) {
            this.onAdLoadFailed = onAdLoadFailed
        }

        fun onAdShow(onAdShow: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdShow = onAdShow
        }

        fun onAdShowFailed(onAdShowFailed: () -> Unit) {
            this.onAdShowFailed = onAdShowFailed
        }

        fun onAdReward(onAdReward: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdReward = onAdReward
        }

        fun onAdClick(onAdClick: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdClick = onAdClick
        }

        fun onAdClose(onAdClose: (atAdInfo: ATAdInfo?, isReward: Boolean, isAgainReward: Boolean) -> Unit) {
            this.onAdClose = onAdClose
        }

        fun onAdAgainShow(onAdAgainShow: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdAgainShow = onAdAgainShow
        }

        fun onAdAgainClick(onAdAgainClick: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdAgainClick = onAdAgainClick
        }

        fun onAdAgainReward(onAdAgainReward: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdAgainReward = onAdAgainReward
        }
    }
}